package controller.thread_pool_solver;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Queue;
import java.util.Random;

import solvers.Hybridiser;
import solvers.Mutator;
import solvers.SimpleHybridiser;
import solvers.SimpleMutator;
import user_interface.FBUserInterface;

import model.ProblemData;
import model.SimpleModel;

public class ThreadPool implements Runnable {
	// Main data
	private final SimpleModel model;
	private final FBUserInterface user_interface;
	private boolean calc_station = false;
	private boolean stop_calc_station = false;
	private boolean pause_station = false;
	// Mutex
	private final Object general_mutex = new Object();
	// Task variables
	private static final Hybridiser HYBRIDISER = SimpleHybridiser.getInstance();
	private static final Mutator MUTATOR = SimpleMutator.getInstance();
	private Thread4ThreadPool threads[] = null;
	private int request_threads_count = 1;
	private int request_max_population = 100;
	private int request_max_iterations = 100;	
	/**
	 * Constructor.
	 */
	public ThreadPool(SimpleModel model, FBUserInterface userInterface) {
		this.model = model;
		this.user_interface = userInterface;
	}
	
	public void calculate(int threadCount, int maxIterations, int maxPopulation) {
		synchronized (general_mutex) {
			if(calc_station) 
				stop_calc_station = true;
			calc_station = true;
			
			this.request_threads_count = threadCount;
			this.request_max_iterations = maxIterations;
			this.request_max_population = maxPopulation;
		}
	}
	
	public void stop() {
		synchronized (general_mutex) {
			if(calc_station) 
				stop_calc_station = true;
			calc_station = false;
			pause_station = false;
		}		
	}
	
	public void pause(boolean state) {
		synchronized (general_mutex) {
			pause_station = state;
		}
	}
	
	@Override
	public void run() {
		while(true) {
			boolean local_calc_station = false;
			synchronized (general_mutex) {
				stop_calc_station = false;
				if(calc_station) {
					local_calc_station = true;
				}
			}
			if(local_calc_station) {
				check_and_kill_old_threads();
				create_new_threads();
				calc();
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {
				// TODO
			}
		}
	}
//=============================================================================
//------------PRIVATE METHODS--------------------------------------------------
	private void calc() {
		ProblemData data = model.getData();
		if(data == null) {
			user_interface.print2console("Please open file with model");
			return;
		}

		Queue<Task> population = new ArrayDeque<>();
		Queue<Result> results = new ArrayDeque<>();
		int max_iterations = 100;
		int max_population = 100;
		synchronized (general_mutex) {
			max_iterations = request_max_iterations;
			max_population = request_max_population;
		}
		
		// generate beginning population
		int n = data.w.length;
		for (int i = 0; i < max_population; i++) {
			Task task = new Task();
			task.p = model.getP();
			task.r = model.getR();
			task.g = data.g;
			task.w = data.w;
			task.x = next_random_solution(n, task.p);
			population.add(task);
		}
		
		// iterations
		user_interface.print2console("Start iterations\n  Begining population = " + max_population + " iterations = " + max_iterations + " threads = " + threads.length);
		Result exit_results[] = null;
		for (int iteration = 0; iteration < max_iterations; iteration++) {				
			check_and_pause();
			if(check_and_stop()) return;
			
			long startEventTime = new Date().getTime(); // [DEBUG PERFORMANCE]
			int i = 0;
			for(Task task : population) {
				threads[i % threads.length].addTask(task);
				i++;
			}
			
			// waiting ending of calculation
			for(i = 0; i < threads.length; i++) {
				results.addAll(threads[i].getResults());
			}
			
			// sort results
			exit_results = new Result[results.size()];
			exit_results = results.toArray(exit_results);
			Arrays.sort(exit_results);
			
			// clear population and result_population
			population.clear();
			results.clear();
			
			// copy leaders
			for(i = 0; i < max_population / 2; i++) {
				Task task = new Task();
				task.p = model.getP();
				task.r = model.getR();
				task.g = data.g;
				task.w = data.w;
				task.x = exit_results[i].getX();
				population.add(task);
			}
			
			// add children with mutations
			for(i = 0; i < max_population / 2; i++) {
				Task task = new Task();
				task.p = model.getP();
				task.r = model.getR();
				task.g = data.g;
				task.w = data.w;
				task.x = HYBRIDISER.hybridise(exit_results[i].getX(), exit_results[i+1].getX(), task.p);
				task.x = MUTATOR.mutate(task.x, task.p);
				population.add(task);
			}
			
			// add random
			for(i = 0; i < max_population / 2; i++) {
				Task task = new Task();
				task.p = model.getP();
				task.r = model.getR();
				task.g = data.g;
				task.w = data.w;
				task.x = next_random_solution(n, task.p);
				population.add(task);
			}
			long finishEventTime = new Date().getTime(); // [DEBUG PERFORMANCE]
			user_interface.print2console((((iteration+1) / (float)max_iterations) * 100) + "% completed\n" + "  Max Income = " + exit_results[0] + "\n  Time = " + ((finishEventTime - startEventTime) / 1000.0) + " sec\n");
			user_interface.setResult(exit_results[0]);
		}
		
		// print results
		user_interface.print2console("Task completed");
		if(exit_results != null && exit_results.length > 0) {
			List<Integer> leader_shops = new ArrayList<Integer>();
			List<Integer> rival_shops = new ArrayList<Integer>();
			for(int i = 0; i < exit_results[0].getX().length; i++) {
				if(exit_results[0].getX()[i] == 1)
					leader_shops.add(i);
				if(exit_results[0].getY()[i] == 1)
					rival_shops.add(i);			
			}
			user_interface.print2console("Leader = " + leader_shops);
			user_interface.print2console("Rival = " + rival_shops);
		}
		
		// set calc_station to false
		synchronized (general_mutex) {
			if(!stop_calc_station)
				calc_station = false;
		}
	}
	
	private int[] next_random_solution(int n, int p) {
		int[] x = new int[n];
		
		Random random = new Random();
		for(int i = 0; i < p; i++) {
			int next = Math.abs(random.nextInt()) % (n - 1);
			if(x[next] == 1) {
				for(int j = 0; j < n; j++) {
					if(x[j] != 1) {
						x[j] = 1;
						break;
					}
				}
			} else {
				x[next] = 1;
			}
		}
		return x;
	}
	
	private void check_and_kill_old_threads() {
		if(threads != null) {
			for(int i = 0; i < threads.length; i++) {
				threads[i].shutdown();
				while(true) {
					if(threads[i].isTerminated()) {
						break;
					} else {
						try {
							Thread.sleep(10);
						} catch (InterruptedException ex) {
							// TODO
						}
					}
				}
			}
		}
	}
	
	/**
	 * This method creates new threads
	 */
	private void create_new_threads() {
		threads = new Thread4ThreadPool[request_threads_count];
		
		for(int i = 0; i < request_threads_count; i++) {
			threads[i] = new Thread4ThreadPool();
			Thread t = new Thread(threads[i]);
			t.setDaemon(true);
			t.start();
		}
	}
	
	private void check_and_pause() {
		while(true) {
			synchronized (general_mutex) {
				if(!pause_station)
					break;
			}
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {
				// TODO
			}
		}
	}
	
	private boolean check_and_stop() {
		synchronized (general_mutex) {
			if(stop_calc_station)
				return true;
			return false;
		}
	}
}
